package wat.clientapp.connection;

import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.future.ReadFuture;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.service.IoHandler;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
import org.apache.mina.filter.ssl.SslContextFactory;
import org.apache.mina.filter.ssl.SslFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import wat.clientapp.main.Parameters;
import wat.ziszcommon.transport.*;

/**
 *
 * @author Tarni & P.B. & L.G. & K.K.
 */
class Connection implements IoHandler {

    private int port = 7000;
    private String nazwaSerw = "localhost"; //chodzi o adres Ip serwera
    private boolean useSSL = false;
    private InetAddress adresSerwera;
    private Socket gniazdo = null;
    private static Connection instance = null;
    private NioSocketConnector connector = null;
    private IoSession session = null;

    public static Connection getInstance() {
        if (instance == null) {
            instance = new Connection();
        }
        return instance;
    }

    private Connection() {
        nazwaSerw = ConnectionParameters.getInstance().getHost();
        port = ConnectionParameters.getInstance().getPort();
        useSSL = ConnectionParameters.getInstance().isUseSSL();

        connector = new NioSocketConnector();
        connector.setConnectTimeoutMillis(30000);
        if(useSSL) {
            try {
                SslFilter sslFilter = new SslFilter(EprOpenSslContextFactory.getInstance(false).getSSLContext());
                sslFilter.setUseClientMode(true);
                connector.getFilterChain().addLast("sslFilter", sslFilter);
                System.out.println("SSL on");
            } catch (NoSuchAlgorithmException ex) {
                Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
            } catch (Exception ex) {
                Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
        connector.setHandler(this);
        connect();
    }

    private Connection(Parameters p) {
        nazwaSerw = ConnectionParameters.getInstance().getHost();
        port = ConnectionParameters.getInstance().getPort();

        /*try {
            adresSerwera = InetAddress.getByName(nazwaSerw);   //Ustawianie adresuIp serwera w formie którą przyjmie Socket
            //gniazdo = new Socket(adresSerwera, Port);        //Tworzenie połaczenia.
        } catch (UnknownHostException ex) {
            Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
            System.out.println("Nie znany / nie znaloziono serwera");
        }*/ //catch (IOException ex) {
        //Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
        //System.out.println("blad IOException");
        //}
    }

    private void connect() {
        ConnectFuture cf = connector.connect(new InetSocketAddress(nazwaSerw, port));
        cf.awaitUninterruptibly();
        /*try {
            gniazdo = new Socket(adresSerwera, Port);        //Tworzenie połaczenia.
        } catch (IOException ex) {
            Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
            System.out.println("błąd połączenia");
            gniazdo = null;
        }*/
    }

    public void disconnect() {
        if(session!=null && session.isConnected()) {
            session.close(true);
        }
        /*if (gniazdo == null) {
            return;
        }
        try {
            gniazdo.close();
        } catch (IOException ex) {
            Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
            System.out.println("błąd rozłączania");
        } finally {
            gniazdo = null;
        }*/
    }

    private String Send(Message m) {
        //Connect();
        ObjectOutputStream out = null;

        try {
            out = new ObjectOutputStream(gniazdo.getOutputStream());
            out.writeObject(m);
            out.flush();
            // !! nie zamykac out, bo to zamknie caly socket i komunikacja upadnie !!
        } catch (IOException ex) {
            Logger.getLogger(String.class.getName()).log(Level.SEVERE, "klient nie wyslal", ex);
            return null;
        }

        //return "pusty";

        ObjectInputStream in = null;
        //w odpowiedzi serwer odpowie stringiem
        try {
            in = new ObjectInputStream(gniazdo.getInputStream());
            String obj = (String) in.readObject();
            return obj;
        } catch (IOException e) {
            Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, "klient nie odebral", e);
            return null;
        } catch (ClassNotFoundException e) {
            Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, "klient odebral nieznana klase", e);
            return null;
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, "bieda z in.close", ex);
            }
            //Disconnect();
        }
    }

    public Object sendMessage(Message msg) {
        if(session == null || !session.isConnected()) {
            connect();
        }
        WriteFuture wf = session.write(msg);
        wf.awaitUninterruptibly();
        ReadFuture rf = session.read();
        rf.awaitUninterruptibly();
        return rf.getMessage();
        /*
        //Connect();
        ObjectOutputStream out = null;

        try {
            out = new ObjectOutputStream(gniazdo.getOutputStream());
            out.writeObject(msg);
            out.flush();
            // !! nie zamykac out, bo to zamknie caly socket i komunikacja upadnie !!
        } catch (IOException ex) {
            Logger.getLogger(String.class.getName()).log(Level.SEVERE, "klient nie wyslal", ex);
            return null;
        }

        //return "pusty";

        ObjectInputStream in = null;
        //w odpowiedzi serwer odpowie stringiem
        try {
            in = new ObjectInputStream(gniazdo.getInputStream());
            return in.readObject();
        } catch (IOException e) {
            Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, "klient nie odebral", e);
            return null;
        } catch (ClassNotFoundException e) {
            Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, "klient odebral nieznana klase", e);
            return null;
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, "bieda z in.close", ex);
            }
            //Disconnect();
        }*/
    }

    /*
     * Metoda na rozpoczecie polaczenia - przygotowuje Public/Private Key Infrastructure
     * na potrzeby szyfrowania danych przesylanych miedzy clientem a serwerem
     */
    /*String preparePKI(String sessionID, String p, String g, String l, String publicKey) {

        return (String) sendMessage(new MessagePKI(sessionID, p, g, l, publicKey));
    }*/

    /*
     * Metoda do logowania nowego uzytkownika rozpoczynajacego sesje. Przesylane parametry userName i password
     * sa zaszyfrowana przy popmocy kluczy uzgodnionych wczesniej przes preparePKI
     */
    public Object login(String sessionID, String userName, String password) {

        return sendMessage(new MessageUsrSession(sessionID, userName, password, MessageUsrSession.Action.LOGIN));
    }

    /*
     * Metoda do podtrzymania polaczenia uzytownika, ktory przez okreslony czas nie wykonal zadnej innej akcji
     * ale ktorego nie nalezy jeszcze wylogowac
     */
   /* String keepAlive(String sessionID) {
        return (String) sendMessage(new MessageUsrSession(sessionID, null, null, MessageUsrSession.Action.KEEP_ALIVE));
    }*/

    /*
     * Metoda do zamniecia sesji uzytkownika (wylogowanie z serwera)
     */
    /*String logout(String sessionID) {
        return (String) sendMessage(new MessageUsrSession(sessionID, null, null, MessageUsrSession.Action.LOGOUT));
    }*/

    String invokeMethod(String sessionID, String methodName, String parameters, String module, String adv) {

        return (String) sendMessage(new MessageMethod(sessionID, methodName, parameters, module, adv));
    }

    public void sessionCreated(IoSession session) throws Exception {
    }

    public void sessionOpened(IoSession session) throws Exception {
        this.session = session;
        session.getConfig().setUseReadOperation(true);
    }

    public void sessionClosed(IoSession session) throws Exception {
        this.session = null;
    }

    public void sessionIdle(IoSession session, IdleStatus idleStatus) throws Exception {
    }

    public void exceptionCaught(IoSession session, Throwable throwable) throws Exception {
        throwable.printStackTrace();
    }

    public void messageReceived(IoSession session, Object msg) throws Exception {
    }

    public void messageSent(IoSession session, Object msg) throws Exception {
    }

    class MyFilenameFilter implements FilenameFilter {

        private String prefix;

        public MyFilenameFilter(String prefix) {
            this.prefix = prefix;
        }

        public boolean accept(File dir, String name) {
            if (name.startsWith(prefix)) {
                return true;
            } else {
                return false;
            }
        }
    }
}
